package qc.module.iot.protocol.modbus;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import qc.common.core.enums.iot.IotPacketsDataFrameTypeEnum;
import qc.common.core.enums.iot.IotPacketsDataProtocolEnum;
import qc.common.core.utils.CrcUtil;
import qc.common.core.utils.RadixConvertUtil;
import qc.module.iot.protocol.IotFrameBodyDataItem;
import qc.module.iot.protocol.IotFrameBodyDataItemTypeEnum;
import qc.module.iot.protocol.IotFrameParseBase;
import qc.module.iot.protocol.IotFrameParseResult;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * 龙江使用的EMS系统发送至水情的机组闸门数据帧协议，基本结构按照Modbus协议
 *
 * @author QuCheng Tech
 * @since 2025/11/16
 */
public class Modbus_Lj_Ems_FrameParse extends IotFrameParseBase {
    private static final Logger logger = LoggerFactory.getLogger(Modbus_Lj_Ems_FrameParse.class);

    //SL651 HEX协议解析参考：https://blog.csdn.net/weixin_42163707/article/details/127638544

    /**
     * 数据帧报文协议
     */
    private static final IotPacketsDataProtocolEnum protocol = IotPacketsDataProtocolEnum.MODBUS_LJ_EMS;

    /**
     * 解析数据帧错误提示信息前缀
     */
    private static final String parseErrorMessagePrefix = "使用龙江监控系统Modbus协议解析数据帧时错误，";

    /**
     * 数据帧最小长度，包含起始符和结束符的最小有效长度
     */
    private static final int Frame_Length_Min_Total = 4;

    //每种数据协议中使用静态定义协议中的数据帧起始符、结束符等，通过构造函数传入到父类中
    /**
     * 数据帧起始符字节
     */
    private static final byte Frame_Start_Byte = 0x01;
    /**
     * 数据帧功能码字节
     */
    private static final byte Frame_Function_Code_Byte = 0x03;

    public Modbus_Lj_Ems_FrameParse() {
        //通过调用父类的构造函数传入协议中的固定内容
        super(protocol, new byte[]{Frame_Start_Byte}, null, Frame_Length_Min_Total);
        //也可以在调用构造函数后显式对变量或调用方法进行赋值
        //this
    }

    /**
     * 解析数据帧
     *
     * @param inputBytes    数据帧报文字节数组，数组起始必须时数据值对应协议的起始符
     * @param parseBodyData 是否解析报文中的正文内容数据，为false时表示只解析数据帧基本结构框架，为true时表示解析解析结构框架和正文内容数据
     * @return qc.module.iot.protocol.IotFrameParseResult
     * @author QuCheng Tech
     * @since 2025/11/5
     */
    @Override
    public IotFrameParseResult parse(byte[] inputBytes, boolean parseBodyData) {
        logger.debug("parseBaseStruct");

        //初始化解析返回结果，设置解析成功标记为false
        IotFrameParseResult result = new IotFrameParseResult();
        result.setParseSuccess(false);
        result.setProtocol(protocol);
        result.setDataTime(null);//初始化数据时标为null
        //输入的字节数组赋值，根据数据协议确定是否要对Hex字符串和Ascii字符串进行赋值
        result.setInputBytes(inputBytes);
        result.setInputHexString(RadixConvertUtil.toHexString(inputBytes));

        /* 江使用的EMS系统发送至水情的机组闸门数据帧协议，基本结构按照Modbus协议，厂家为江河机电，不是标准的协议
         * 结构框架：站号 功能码 数据长度 数据 CRC
         * 长度字节： 1    1      1     n   2
         * 固定内容： 站号-01、功能码-03
         * 其中的每个数据均使用2个字节，直接使用十六进制转换为十进制即为数值
         * 解析举例：01 03 1A 0014000000090000001A0000000000000000000000000353032A F611
         * 解析举例：01 03 1A 00090000FFFE0000001A0000000000000000000000000353032A B63C
         */
        if (inputBytes == null || inputBytes.length < this.frameMinLength)
            return returnParseError(result, "数据帧报文内容为空");
        //记录当前解析判断到的位置索引，数据帧的每个部分在解析前均要判断输入的字节数组总长是否足够
        int index = 0x0;
        //判断有效数据帧报文最小总长度
        int frameMinTotalLength = 0x0;
        //数据帧的每个结构框架部分的长度，判断到每个部分长度时进行赋值
        int framePartLength = 0x0;

        //站号，长度为1字节
        framePartLength = 0x1;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的站号时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        byte stationCodeByte = inputBytes[index];
        result.setStationAddressBytes(new byte[]{stationCodeByte});
        result.setStationAddress(RadixConvertUtil.toHexString(stationCodeByte));
        if (inputBytes[index] != Frame_Start_Byte)
            return returnParseError(result, "数据帧中的站号错误");
        index += framePartLength;//站号判断通过，索引加上站号长度

        //功能码，长度为1字节
        framePartLength = 0x1;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的功能码时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        byte functionCodeByte = inputBytes[index];
        result.setFunctionCodeBytes(new byte[]{functionCodeByte});
        result.setFunctionCode(RadixConvertUtil.toHexString(functionCodeByte));
        if (inputBytes[index] != Frame_Function_Code_Byte)
            return returnParseError(result, "数据帧中的功能码错误");
        index += framePartLength;//功能码判断通过，索引加上功能码长度

        //数据长度，长度为1字节
        framePartLength = 0x1;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的数据长度时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        byte contentLengthByte = inputBytes[index];
        int contentLength = RadixConvertUtil.computeIntValue(contentLengthByte);
        result.setBodyLength(contentLength);
        index += framePartLength;//帧遥测站地址判断通过，索引加上帧遥测站地址长度
        //根据内容长度判断报文长度是否足够
        if (contentLength > 0x0) {
            //logger.debug("报文中内容正文长度解析为：" + contentLength);
            //Modbus协议中的数据长度是指数据域的字节数，不包含前面的站点、功能码和数据长度字节，也不包含最后的CRC的2字节
            frameMinTotalLength = index + contentLength;
            if (inputBytes.length < frameMinTotalLength)
                return returnParseError(result, "根据报文中的正文长度[" + contentLength + "]判断报文长度不足，报文总长度应至少为" + frameMinTotalLength);

            //根据报文正文长度获取正文字节数组
            byte[] userDataBytes = Arrays.copyOfRange(inputBytes, index, index + contentLength);
            String userDataHexString = RadixConvertUtil.toHexString(userDataBytes);
            logger.debug("报文中内容正文HEX为：" + userDataHexString);
            result.setBodyDataBytes(userDataBytes);
            result.setBodyDataString(userDataHexString);
            index += contentLength;//索引加上报文正文长度
        }

        //CRC，长度为2字节
        framePartLength = 0x2;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的CRC校验码时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        byte[] crcBytes = Arrays.copyOfRange(inputBytes, index, index + framePartLength);
        result.setCrcBytes(crcBytes);
        //判断CRC是否正确
        int crcIntValue = RadixConvertUtil.computeIntValue(RadixConvertUtil.computeIntValue(crcBytes[0x0]), RadixConvertUtil.computeIntValue(crcBytes[0x1]));
        byte[] calcCrcBytes = Arrays.copyOfRange(inputBytes, 0x0, index);//计算CRC输入的字节数组不包含crc的2个字节
        int calcCrcIntValue = CrcUtil.Crc16_Modbus(calcCrcBytes);
        //logger.debug("计算CRC，报文：" + RadixConvertUtil.toHexString(calcCrcBytes));
        //logger.debug("报文中CRC值为：" + crcIntValue + "，计算出的CRC值为：" + calcCrcIntValue + "，计算出的CRC字符串为：" + String.format("%04X", calcCrcIntValue));
        if (crcIntValue != calcCrcIntValue) {
            //计算出的crc与报文中的crc不一致
        }

        index += framePartLength;//报文CRC校验码判断通过，索引加上CRC校验码的长度

        //全部判断和验证均通过，解析成功；设置完整的数据帧字节数组
        result.setParseSuccess(true);
        byte[] frameBytes = Arrays.copyOfRange(inputBytes, 0x0, index);
        result.setParseFrameBytes(frameBytes);
        result.setParseFrameString(RadixConvertUtil.toHexString(frameBytes));

        //解析成功时固定设置结果中的数据帧类型，Modbus只能发送数据
        IotPacketsDataFrameTypeEnum frameType = IotPacketsDataFrameTypeEnum.DATA;
        result.setFrameType(frameType);
        result.setFrameName(frameType.getName());

        //判断如果要解析正文中的数据进行解析
        if (parseBodyData == true && result.getBodyDataBytes() != null && result.getBodyDataBytes().length > 0x0) {
            //其中的每个数据均使用2个字节，直接使用十六进制转换为十进制即为数值，0x0000~0xFFFF表示正整数0~65535
            //举例 hex：0014 0000 0009 0000 001A 0000 0000 0000 0000 0000 0000 0353 032A
            //对应值：    20   0    9    0    26   0    0    0    0    0    0   851  810
            List<IotFrameBodyDataItem> bodyDataItems = new LinkedList<>();
            for (int i = 1; i <= result.getBodyDataBytes().length / 0x2; i++) {//数据序号，从1开始
                byte[] elementDataValueBytes = new byte[]{result.getBodyDataBytes()[(i - 0x1) * 0x2], result.getBodyDataBytes()[i * 0x2 - 0x1]};
                int intValue = RadixConvertUtil.computeIntValue(elementDataValueBytes);
                //先初始化一个数据值的项
                IotFrameBodyDataItem elementDataValueItem = new IotFrameBodyDataItem();
                elementDataValueItem.setBytes(elementDataValueBytes);
                elementDataValueItem.setHexString(RadixConvertUtil.toHexString(elementDataValueBytes));
                elementDataValueItem.setType(IotFrameBodyDataItemTypeEnum.VALUE);
                //名称和编码均使用序号
                elementDataValueItem.setName(String.valueOf(i));
                elementDataValueItem.setCode("data" + i);//使用data1、data2...作为编码
                elementDataValueItem.setValue(intValue);

                bodyDataItems.add(elementDataValueItem);
            }
            result.setBodyDataItems(bodyDataItems);
        }

        logger.info("报文解析成功，正文长度[{}]，中心站地址[{}]，遥测站地址[{}]，密码[{}]，功能码[{}]，帧名称[{}]，帧类型[{}]，正文内容[{}]",
                result.getBodyLength(), result.getCenterAddress(), result.getStationAddress(), result.getPassword(),
                result.getFunctionCode(), result.getFrameName(), result.getFrameType(), result.getBodyDataString());

        return result;
    }

    /**
     * 返回数据帧解析错误结果
     *
     * @param result   返回解析结果对象
     * @param errorMsg 错误信息，返回结果中自动加上统一的前缀
     * @return qc.module.iot.protocol.IotFrameParseResult
     * @author QuCheng Tech
     * @since 2025/11/7
     */
    IotFrameParseResult returnParseError(IotFrameParseResult result, String errorMsg) {
        if (result == null) {
            result = new IotFrameParseResult();
            result.setParseSuccess(false);
        }

        result.setParseErrorMsg(parseErrorMessagePrefix + errorMsg);

        return result;
    }
}
